
#include "rapidxml.hpp"
#include "xml_loader.hpp"
#include "xml_node.hpp"
#include "xml_conv.hpp"

// stl
#include <iostream>
#include <fstream>
#include <vector>

namespace mp
{
	namespace xml
	{
		class rapidxml_loader 
		{
		public:
			rapidxml_loader(const char *encoding = NULL) :
			  filename_()
			  {

			  }

			  ~rapidxml_loader()
			  {
			  }

			  bool load(std::string const& filename, xml_node &node)
			  {
				  //if (!mapnik::util::exists(filename))
				  //{
				  //    throw config_error(std::string("Could not load map file: File does not exist"), 0, filename);
				  //}
				  filename_ = filename;
//#ifdef _WINDOWS
//				  std::basic_ifstream<char> stream(utf8_to_utf16(filename));
//#else
				  std::basic_ifstream<char> stream(filename.c_str());
//#endif
				  if (!stream)
				  {
					  return false;
					  //throw config_error("Could not load map file", 0, filename);
				  }
				  stream.unsetf(std::ios::skipws);
				  std::vector<char> v(std::istreambuf_iterator<char>(stream.rdbuf()),
					  std::istreambuf_iterator<char>());
				  if (!stream.good())
				  {
					  return false;
					  //throw config_error("Could not load map file", 0, filename_);
				  }
				  v.push_back(0); // zero-terminate
				 return load_array(v, node);
			  }

			  template <typename T>
			  bool load_array(T & array, xml_node & node)
			  {
				  try
				  {
					  // Parse using appropriate flags
					  // https://github.com/mapnik/mapnik/issues/1856
					  // const int f_tws = rapidxml::parse_normalize_whitespace;
					  const int f_tws = rapidxml::parse_trim_whitespace | rapidxml::parse_validate_closing_tags;
					  rapidxml::xml_document<> doc;
					  //doc.parse<f_tws>(&array.front());
					  doc.parse<f_tws>(&*array.begin());

					  for (rapidxml::xml_node<char> *child = doc.first_node();
						  child; child = child->next_sibling())
					  {
						  populate_tree(child, node);
					  }
				  }
				  catch (rapidxml::parse_error const& e)
				  {
					  //long line = static_cast<long>(
					  //    std::count(&array.front(), e.where<char>(), '\n') + 1);
					  //throw config_error(e.what(), line, filename_);
					  return false;
				  }

				  return true;
			  }

			  bool load_string(std::string const& buffer, xml_node &node, std::string const & )
			  {
				  // Note: base_path ignored because its not relevant - only needed for xml2 to load entities (see libxml2_loader.cpp)
				  return load_array(std::string(buffer), node);
			  }
		private:
			void populate_tree(rapidxml::xml_node<char> *cur_node, xml_node &node)
			{
				switch (cur_node->type())
				{
				case rapidxml::node_element:
					{
						xml_node &new_node = node.add_child((char *)cur_node->name(), 0, false);
						// Copy attributes
						for (rapidxml::xml_attribute<char> *attr = cur_node->first_attribute();
							attr; attr = attr->next_attribute())
						{
							new_node.add_attribute(attr->name(), attr->value());
						}

						// Copy children
						for (rapidxml::xml_node<char> *child = cur_node->first_node();
							child; child = child->next_sibling())
						{
							populate_tree(child, new_node);
						}
					}
					break;

					// Data nodes
				case rapidxml::node_data:
				case rapidxml::node_cdata:
					{
						if (cur_node->value_size() > 0) // Don't add empty text nodes
						{
							node.add_child(cur_node->value(), 0, true);
						}
					}
					break;
				default:
					break;
				}
			}
		private:
			std::string filename_;
		};

		bool read_xml(std::string const & filename, xml_node &node)
		{
			rapidxml_loader loader;
			return loader.load(filename, node);
		}
		bool read_xml_string(std::string const & str, xml_node &node, std::string const & base_path)
		{
			rapidxml_loader loader;
			return loader.load_string(str, node, base_path);
		}
	}
}
